﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace DARE
{
    public class CCurve
    {

        #region fields

        // subs parts of the curve to compute the distance
        private const int m_hash = 20;

        private Vector3 m_start;
        private Vector3 m_ctrlA;
        private Vector3 m_ctrlB;
        private Vector3 m_end;
        private bool m_needUpdate;
        private float m_length;

        #endregion

        #region properties

        public Vector3 Start
        {
            get { return m_start; }
            set
            {
                if (m_start != value)
                {
                    m_start = value;
                    m_needUpdate = true;
                }
            }
        }

        public Vector3 CtrlA
        {
            get { return m_ctrlA; }
            set
            {
                if (m_ctrlA != value)
                {
                    m_ctrlA = value;
                    m_needUpdate = true;
                }
            }
        }

        public Vector3 CtrlB
        {
            get { return m_ctrlB; }
            set
            {
                if (m_ctrlB != value)
                {
                    m_ctrlB = value;
                    m_needUpdate = true;
                }
            }
        }

        public Vector3 End
        {
            get { return m_end; }
            set
            {
                if (m_end != value)
                {
                    m_end = value;
                    m_needUpdate = true;
                }
            }
        }

        public float Length
        {
            get
            {
                if (m_needUpdate)
                    ComputeLength();
                return m_length;
            }
        }

        #endregion

        #region ctor

        public CCurve()
        {
            Initialize(Vector3.Zero, Vector3.Zero, Vector3.Zero, Vector3.Zero);
        }

        public CCurve(Vector3 start, Vector3 ctrlA, Vector3 ctrlB, Vector3 end)
        {
            Initialize(start, ctrlA, ctrlB, end);
        }

        private void Initialize(Vector3 start, Vector3 ctrlA, Vector3 ctrlB, Vector3 end)
        {
            m_length = 0;
            m_start = start;
            m_ctrlA = ctrlA;
            m_ctrlB = ctrlB;
            m_end = end;
            m_needUpdate = true;
        }

        #endregion

        #region methods

        /// <summary>
        /// Get the position in the curve
        /// </summary>
        /// <param name="t">t is between 0 (start point) et 1 (end point)</param>
        /// <returns></returns>
        public Vector3 GetPos(float t)
        {
            return m_start * (1 - t) * (1 - t) * (1 - t) + 
                m_ctrlA * 3* t * (1 - t) * (1 - t) + 
                m_ctrlB * 3 * t * t * (1 - t) + 
                m_end * t * t * t;
        }

        public Vector3 GetTangent(float t)
        {
            // =========================================

            // 3 (1-t) ^ 2 (-1) P0
            // + [6 (1-t) (-1) 3 t (1-t) ^ 2] P1
            // + [3 (-1) t ^ 2 6 (1-t) t] P2 3 2P3 ^ t 

            //Vector3 end = new Vector3(
            //    (float)Math.Pow(m_end.X, t), 
            //    (float)Math.Pow(m_end.Y, t), 
            //    (float)Math.Pow(m_end.Z, t));
            //return 3 * (1 - t) * (1 - t) * -1 * m_start +
            //    (6 * (1 - t) * -1 * 3 * t * (1 - t) * (1 - t)) * m_ctrlA +
            //    (3 * -1 * t * t * 6 * (1 - t) * t) * m_ctrlB *
            //    3 * 2 * end;

            // =========================================

            // 3 * x^2 * D + 
            // (6 * x - 9 * x^2) * C + 
            // (3 - 6 * x) * B + 
            // (-3 * x^2 + 6 * x - 3) * A

            //return 3 * t * t * m_end +
                //(6 * t - 9 * t * t) * m_ctrlB +
                //(3 - 6 * t) * m_ctrlA +
                //(-3 * t * t + 6 * t - 3) * m_start;

            // =========================================

            //-6*(A*(x-1)+B+3*C*x-C-D*x)

            return -6 * (m_start * (t - 1) +
                m_ctrlA +
                3 * m_ctrlB * t - m_ctrlB - m_end * t);
        }

        private void ComputeLength()
        {
            int eval = (int)Math.Ceiling(
                Vector3.Distance(m_start, m_ctrlA) +
                Vector3.Distance(m_ctrlA, m_ctrlB) +
                Vector3.Distance(m_ctrlB, m_end));
            float step = 1.0f / (float)(eval * m_hash);
            Vector3 lastPos = GetPos(0);
            Vector3 curPos = Vector3.Zero;

            m_length = 0;
            for (float t = step; t <= 1; t += step)
            {
                m_length += Vector3.Distance(lastPos, (curPos = GetPos(t)));
                lastPos = curPos;
            }
            m_needUpdate = false;
        }

        #endregion

    }
}
